[BITS 16] 
[ORG 0x8000]

jmp		0x0000:START
START:
	; clear the screen & set the video mode
	mov		ax, 0x0003
	int		0x10

	; greet
	mov		si, MsgHello
	call	PrintString

	; load kernel image to 0x1000:0x0000 (0x10000)
	mov		si, MsgLoadKernelImage
	call	PrintString
	mov		si, FileKernelImage
	mov		bx, 0x1000
	mov		di, 0x0000
	call	LoadFile
	jc		LOAD_FILE_ERROR
	mov		si, MsgDone
	call	PrintString

	; load storage driver image to 0x7000:0x0000 (0x70000)
	mov		si, MsgLoadStorageImage
	call	PrintString
	mov		si, FileStorageImage
	mov		bx, 0x7000
	mov		di, 0x0000
	call	LoadFile
	jc		LOAD_FILE_ERROR
	mov		si, MsgDone
	call	PrintString

	; load FAT32 driver image to 0x8000:0x0000 (0x80000)
	mov		si, MsgLoadFAT32Image
	call	PrintString
	mov		si, FileFAT32Image
	mov		bx, 0x8000
	mov		di, 0x0000
	call	LoadFile
	jc		LOAD_FILE_ERROR
	mov		si, MsgDone
	call	PrintString

	; load startup process image to 0x9000:0x0000 (0x90000)
	mov		si, MsgLoadSetupImage
	call	PrintString
	mov		si, FileSetupImage
	mov		bx, 0x9000
	mov		di, 0x0000
	call	LoadFile
	jc		LOAD_FILE_ERROR
	mov		si, MsgDone
	call	PrintString
	
	

	; generate the system memory map at 0x0000:0xf000
	mov		ax, 0x0000
	mov		es, ax
	mov		di, 0xf000
	call	do_e820
	jc		MEM_MAP_ERROR
	mov		[0xfffe], bp

	; open A20 gate
	mov		ax, 0x2401			; ax = 0x2401 for fast A20 open
	int		0x15				; call it
	jc		A20_ERROR

	; check for long mode availability
	; use CPUID to check if long mode is supported
	; check for cpuid extentions
	mov 	eax, 0x80000000 	; Set the A-register to 0x80000000.
	cpuid                  		; CPU identification.
    cmp 	eax, 0x80000001		; Compare the A-register with 0x80000001.
    jb 		NO_LONG_MODE		; It is less, there is no long mode.

	; check for long mode
	mov		eax, 0x80000001		; Set the A-register to 0x80000001.
	cpuid                  		; CPU identification.
	test	edx, 1 << 29      	; Test if the LM-bit, which is bit 29, is set in the D-register.
	jz		NO_LONG_MODE        ; They aren't, there is no long mode.

	; long mode is supported, continue
	; setup for mininal protected mode environment
	cli							; disable interrupts
	lgdt	[prmd_gdt_desc]		; load basic GDT
	mov		eax, cr0			; get cr0
	or		ax, 1				; set protected mode switch
	mov		cr0, eax			; save it back in cr0
	jmp		0x08:START32		; jump to 32 bit segment to enable 32 bit protected mode

	
;***************************************************************************
; 32-bit section
[BITS 32]

START32:
	; setup the data segment
	mov		ax, 0x10
	mov		ds, ax
	mov		es, ax

	; now enable long mode
	; need to create basic page tables (identity map the first 16 mb)
	; put the page table at 0x1000
	mov		edi, 0x1000			; put the plm4 address in edi
	mov		cr3, edi			; give cr3 the location of the page table pml4

	; create a pml4 entry
	mov		eax, 0x1b + 0x2000	; flags + address of pdpe entry
	xor		ebx, ebx			; set high half to 0
	mov		[edi], eax			; write low half
	mov		[edi + 4], ebx		; write high half

	; create a pdpe entry at 0x2000
	mov		edi, 0x2000
	mov		eax, 0x301b			; flags + address
	mov		[edi], eax			; write low half
	mov		[edi + 4], ebx		; write high half

	; create 8 pde entries at 0x3000
	mov		edi, 0x3000
	mov		eax, 0x9b			; flags + 0 = identity map 1st 2mb
	mov		ecx, 8				; 8 entries

.pde_loop:
	; loop through to create entries
	mov		[edi], eax			; write low half
	mov		[edi + 4], ebx		; write high half
	add		eax, 0x200000		; add 2mb to point to next page
	add		edi, 8				; point to next entry
	loop	.pde_loop			; aaand again

	; enable PAE
	mov		eax, cr4
	or		al, 1 << 5
	mov		cr4, eax

	; enable long mode
	mov		ecx, 0xc0000080
	rdmsr
	or		ax, 1 << 8
	wrmsr

	; enable paging
	mov		eax, cr0
	or		eax, 1 << 31
	mov		cr0, eax

	; load the long mode GDT
	lgdt	[long_gdt_desc]

	; jump to long mode code
	jmp		0x08:START64

;***************************************************************************
[BITS 64]
START64:
	; now in long mode
	; setup temporary segments
	mov		ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ax, 0
	mov		ss, ax

	; move kernel code to 0x200000
	; image located at 0x10000 (384k)
	mov		rcx, 0xc000				; move 49,152 qwords
	mov		rdi, 0x200000			; start destination
	mov		rsi, 0x10000			; start source

MOVE_KERNEL:
	; kernel move copy loop
	mov		rax, [rsi]
	mov		[rdi], rax
	add		rdi, 8
	add		rsi, 8
	loop	MOVE_KERNEL
	
	; move storage driver code to 0x400000
	; image located at 0x70000 (64k)
	mov		rcx, 0x2000
	mov		rdi, 0x400000
	mov		rsi, 0x70000

MOVE_STORAGE:
	; storage driver move loop
	mov		rax, [rsi]
	mov		[rdi], rax
	add		rdi, 8
	add		rsi, 8
	loop	MOVE_STORAGE

	; move FAT32 driver code to 0x600000
	; image located at 0x80000 (64k)
	mov		rcx, 0x2000
	mov		rdi, 0x600000
	mov		rsi, 0x80000

MOVE_FAT32:
	; move FAT32 driver loop
	mov		rax, [rsi]
	mov		[rdi], rax
	add		rdi, 8
	add		rsi, 8
	loop	MOVE_FAT32
	
	; move startup process code to 0x800000
	; image located at 0x90000 (64k)
	mov		rcx, 0x2000
	mov		rdi, 0x800000
	mov		rsi, 0x90000

MOVE_STARTUP:
	; startup code move loop
	mov		rax, [rsi]
	mov		[rdi], rax
	add		rdi, 8
	add		rsi, 8
	loop	MOVE_STARTUP



	; now create the initial kernel memory map at 0xe00000
	; PML4 at 0xe00000 (14MB)
	; first zero all 512 entries
	; setup for zero loop
	mov		rdi, 0xe00000
	mov		rax, 0
	mov		rcx, 512

PML4_ZERO_LOOP:
	; zero the entry and move to next
	mov		[rdi], rax
	add		rdi, 8
	loop	PML4_ZERO_LOOP

	; now point last PML4 entry (entry 511)
	; at the kernel PDPE at 0xe03000
	mov		rdi, 0xe00ff8
	mov		rax, 0xe03000 + 0x3
	mov		[rdi], rax

	; build kernel PDPEs
	; setup zero loop
	mov		rdi, 0xe03000
	mov		rax, 0
	mov		rcx, 512

PDPE_ZERO_LOOP:
	; zero the entry and move to next
	mov		[rdi], rax
	add		rdi, 8
	loop	PDPE_ZERO_LOOP

	; map first entry to 0xe04000
	mov		rdi, 0xe03000
	mov		rax, 0xe04000 + 0x3
	mov		[rdi], rax

	; zero all entries in first kernel PD at 0xe04000
	; setup for loop
	mov		rdi, 0xe04000
	mov		rax, 0
	mov		rcx, 512

PDTE_ZERO_LOOP:
	; zero entry and move to next
	mov		[rdi], rax
	add		rdi, 8
	loop	PDTE_ZERO_LOOP

	; now map low 16 MB into the first 8 entries
	; setup for loop
	mov		rdi, 0xe04000
	mov		rax, 0x183
	mov		rcx, 8

PDTE_16_MAP_LOOP:
	; set entry and move on to next
	mov		[rdi], rax
	add		rdi, 8
	add		rax, 0x200000
	loop	PDTE_16_MAP_LOOP


	; now need to set up temporary identity mapping
	; for 0 - 4MB
	; point first PML4 entry to ID mapping PDP at 0xe01000
	mov		rdi, 0xe00000
	mov		rax, 0xe01000 + 0x3
	mov		[rdi], rax

	; now setup ID PDP at 0xe01000
	; need to clear all entries
	; setup for clear loop
	mov		rdi, 0xe01000
	mov		rax, 0
	mov		rcx, 512

PDP_ID_ZERO_LOOP:
	; zero entry and move to next
	mov		[rdi], rax
	add		rdi, 8
	loop	PDP_ID_ZERO_LOOP

	; now create entry to point to ID PD at 0xe02000
	mov		rdi, 0xe01000
	mov		rax, 0xe02000 + 0x3
	mov		[rdi], rax

	; now zero the ID PD entries at 0xe02000
	mov		rdi, 0xe02000
	mov		rax, 0
	mov		rcx, 512

PD_ID_ZERO_LOOP:
	; zero entry and move to next
	mov		[rdi], rax
	add		rdi, 8
	loop	PD_ID_ZERO_LOOP

	; now create mapping for 0 - 4MB in PD
	mov		rdi, 0xe02000
	mov		rax, 0x83
	mov		[rdi], rax
	add		rax, 0x200000
	add		rdi, 8
	mov		[rdi], rax

	; load CR3
	mov		rax, 0xe00000
	mov		cr3, rax

	; enable SSE and the like
	mov 	rax, cr0
	and 	ax, 0xfffb			; clear coprocessor emulation CR0.EM
	or 		rax, 0x2			; set coprocessor monitoring CR0.MP
	mov 	cr0, rax
	mov 	rax, cr4
	or 		rax, (3 << 9)		; set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
	or		rax, (1 << 7)		; enable global pages
	mov 	cr4, rax

	; jump to the kernel setup code
	mov		rax, 0xffffff8000200000
	jmp		rax
	


;***************************************************************************
; strings
[BITS 16]
	
MsgHello				db "--Apollo 0.5+ Bootloader--",13,10,0
MsgLoadKernelImage		db 13,10,"Loading kernel image...",0
MsgLoadStorageImage		db 13,10,"Loading storage driver image...",0
MsgLoadFAT32Image		db 13,10,"Loading FAT32 driver image...",0
MsgLoadSetupImage		db 13,10,"Loading startup process image...",0
MsgDone					db " Done!",0
MsgMemoryError			db " Error!",13,10,13,10,"Could not get the system memory map",0
MsgA20Error				db " Error!",13,10,13,10,"Could not open the A20 gate",0
MsgReadError			db " Error!",13,10,13,10,"Could not load the file.",0
MsgLongModeError		db 13,10,13,10,"Error! 64-bit mode is not available on this processor.",0

FileKernelImage			db "KERNEL  SYS",0
FileStorageImage		db "AHCI    SYS",0
FileFAT32Image			db "FAT32   SYS",0
FileSetupImage			db "STARTUP SYS",0

;***************************************************************************
; some defines to make things easier
; Apollo 0.5 bootloader data table
;times 400 - ($ - $$) db 0

;FatTableStartSector			dd 0
;FatDataAreaStartSector			dd 0
;FatClusterSize					dd 0
;Fat512SectorsPerCluster		dd 0
;FatRootCluster					dd 0
;reserved						dd 0, 0
;DriveNumber					dd 0
;OSSignature					dd 0x352e3041	; "A0.5"
;PartitionScheme				dd 0			; 0 = MBR, 1 = GPT
;BootPartitionStartSector		dd 0
;BootloaderVersionNumber		dw 0

%define DATA_TABLE_BASE				(0x7c00 + 400)
%define FatTableStartSector			(DATA_TABLE_BASE + 0)
%define FatDataAreaStartSector		(DATA_TABLE_BASE + 4)
%define FatClusterSize				(DATA_TABLE_BASE + 8)
%define Fat512SectorsPerCluster		(DATA_TABLE_BASE + 12)
%define FatRootCluster				(DATA_TABLE_BASE + 16)
%define reserved					(DATA_TABLE_BASE + 20)
%define DriveNumber					(DATA_TABLE_BASE + 28)
%define OSSignature					(DATA_TABLE_BASE + 32)
%define PartitionScheme				(DATA_TABLE_BASE + 36)
%define BootPartitionStartSector	(DATA_TABLE_BASE + 40)
%define BootloaderVersionNumber		(DATA_TABLE_BASE + 44)


;***************************************************************************
; 16-bit error outs

NO_LONG_MODE:
	mov		si, MsgLongModeError
	call	PrintString
	jmp		ERROR

A20_ERROR:
	mov		si, MsgA20Error
	call	PrintString
	jmp		ERROR

MEM_MAP_ERROR:
	; error occurred getting the memory map
	mov		si, MsgMemoryError
	call	PrintString
	jmp		ERROR

LOAD_FILE_ERROR:
	; error occurred loading the file
	mov		si, MsgReadError
	call	PrintString

ERROR:
	cli
	hlt

;***************************************************************************
; 16-bit functions

;***************************************************************************
; function to get the memory map via BIOS int 0x15:e820
; used from OSDev wiki with thanks :) http://wiki.osdev.org/Detecting_Memory_%28x86%29#Getting_an_E820_Memory_Map
; use the INT 0x15, eax= 0xE820 BIOS function to get a memory map
; inputs: es:di -> destination buffer for 24 byte entries
; outputs: bp = entry count, trashes all registers except esi

do_e820:
	xor 	ebx, ebx			; ebx must be 0 to start
	xor		bp, bp				; keep an entry count in bp
	mov 	edx, 0x0534D4150	; Place "SMAP" into edx
	mov 	eax, 0xe820
	mov 	[di + 20], dword 1	; force a valid ACPI 3.X entry
	mov 	ecx, 24				; ask for 24 bytes
	int 	0x15
	jc 		short .failed		; carry set on first call means "unsupported function"
	mov 	edx, 0x0534D4150	; Some BIOSes apparently trash this register?
	cmp 	eax, edx			; on success, eax must have been reset to "SMAP"
	jne 	short .failed
	test 	ebx, ebx			; ebx = 0 implies list is only 1 entry long (worthless)
	je 		short .failed
	jmp 	short .jmpin

.e820lp:
	mov 	eax, 0xe820			; eax, ecx get trashed on every int 0x15 call
	mov 	[di + 20], dword 1	; force a valid ACPI 3.X entry
	mov 	ecx, 24				; ask for 24 bytes again
	int 	0x15
	jc 		short .e820f		; carry set means "end of list already reached"
	mov 	edx, 0x0534D4150	; repair potentially trashed register

.jmpin:
	jcxz 	.skipent			; skip any 0 length entries
	cmp 	cl, 20				; got a 24 byte ACPI 3.X response?
	jbe 	short .notext
	test 	byte [di + 20], 1	; if so: is the "ignore this data" bit clear?
	je 		short .skipent

.notext:
	mov 	ecx, [di + 8]		; get lower dword of memory region length
	or 		ecx, [di + 12]		; "or" it with upper dword to test for zero
	jz 		.skipent			; if length qword is 0, skip entry
	inc 	bp					; got a good entry: ++count, move to next storage spot
	add 	di, 24

.skipent:
	test	ebx, ebx			; if ebx resets to 0, list is complete
	jne 	short .e820lp

.e820f:
	clc
	ret

.failed:
	stc
	ret

;***************************************************************************
; function to load a file from the given root cluster
; the root cluster number will point to the cluster of
; whereever the boot files are located
; si = file name
; bx:di = destination

LoadFile:
	; save the file name & destination
	push	bx
	push	di
	push	si

	; get root cluster number
	mov		eax, [FatRootCluster]

.load_search_cluster:
	; eax = cluster to scan
	push	eax				; save cluster number
	xor		bx, bx			; clear destination segment
	mov		di, 0x1000		; set destination offset
	call	ReadCluster		; call the function

	; set up for loop
	mov		di, 0x1000		; base of cluster
	mov		si, sp			; get stack ptr
	mov		si, [si + 4]	; get file name
	xor		dx, dx			; clear counter

.search_cluster_dirs:
	; check names in this cluster
	mov		cx, 11			; file name length
	call	CompString

	jnc		.found_file_entry

	; not this entry
	add		di, 32
	add		dx, 32
	cmp		dx, WORD [FatClusterSize]
	jne		.search_cluster_dirs

	; need to load the next cluster
	pop		eax						; retrieve cluster number
	call	GetNextCluster			; get the next cluster number
	jnc		.load_search_cluster	; get the next one to load

	add		sp, 6					; balance stack
	stc
	ret								; exit on error, or end of chain

.found_file_entry:
	; balance the stack
	add		sp, 6

	; di points to this entry's base
	; get start cluster number
	mov		ax, [di + 20]
	shl		eax, 16
	mov		ax, [di + 26]
	
	; get destination
	pop		di
	pop		bx

.load_loop:
	; bx:di = destination for file
	; eax = cluster to load
	; save them
	push	bx
	push	di
	push	eax

	; load the cluster
	call	ReadCluster

	; restore cluster number & get next cluster
	pop		eax
	call	GetNextCluster
	jc		.finish

	; increase pointers
	pop		di							; retrieve offset
	pop		bx							; retrieve segment
	add		di, WORD [FatClusterSize]	; add cluster size to offset
	;or		di, di						; check for rollover to zero
	jnz		.load_loop					; loop if not rolled-over
	add		bx, 0x1000					; point bx to the next segment if did roll over
	jmp		.load_loop					; loop


.finish:
	pop		bx
	pop		di
	clc
	ret



;***************************************************************************
; function to read a cluster
; eax = cluster number
; bx:di = destination
; sets carry on error

ReadCluster:
	; need to calculate start sector number
	sub		eax, 2									; subtract 2 from cluster number
	mov		ecx, [Fat512SectorsPerCluster]			; get number of sectors per cluster
	mul		ecx										; multiply the above together
	add		eax, DWORD [FatDataAreaStartSector]		; add offset to start of data area

	; read the sectors in
	call	ReadSectors
	jnc		.finish

.error:
	stc
	ret

.finish:
	ret

;***************************************************************************
; function to get next cluster in chain
; loads sectors to 0x7e00
; eax = current cluster
; returns eax = next cluster
; sets carry if no more clusters in chain

GetNextCluster:
	; first calculate which sector offset to load (from base of FAT table)
	; and the DWORD index into that sector that contains the next cluster number
	xor		edx, edx		; clear top 64 bits
	mov		ecx, 128		; divisor (128 4-byte enties in a 512-byte sector)
	div		ecx				; perform division

	; eax = sector number
	; edx = entry number
	add		eax, [FatTableStartSector]	; add the base address of the FAT table start
	push	dx							; save the entry number
	mov		cx, 1						; want 1 sector
	xor		bx, bx						; destination segment 0x0000
	mov		di, 0x7e00					; destination offset 0x7e00
	call	ReadSectors					; call the function
	pop		di							; restore entry number
	jc		.error						; error out

	; now read the value and return it, if valid
	shl		di, 2						; multiply index by 4
	add		di, 0x7e00					; add offset
	mov		eax, [di]					; get the value
	and		eax, 0x0fffffff				; mask top 4 bits
	or		eax, eax					; check for zero
	jz		.error						; error out if zero
	cmp		eax, 0x0ffffff7				; check for EoC (End of Chain)
	jb		.finish						; if is fine, return it. otherwise error

.error:
	stc
	ret

.finish:
	clc
	ret

;***************************************************************************
; function to read sectors from drive
; eax = sector start
; cx = sector count
; bx = destination segment
; di = destination offset

; DAP
DAP:
.size		db 16
.zero		db 0
.count		dw 0
.offset		dw 0
.segment	dw 0
.sector		dq 0

; fuction
ReadSectors:
	; save parameters to the DAP
	mov		[DAP.count], cx
	mov		[DAP.segment], bx
	mov		[DAP.offset], di
	mov		[DAP.sector], eax

	; setup for drive reset (5 attempts)
.reset_loop:
	mov		cx, 5				; 5 attempts
	xor		ah, ah				; reset disk system command
	mov		dl, [DriveNumber]	; get disk drive number
	int		0x13				; call BIOS
	jnc		.reset_success		; leave if reset was success
	loop	.reset_loop			; try again if not
	jmp		.error				; if loop fell through, could not reset the disk

	; drive was successfully reset
.reset_success:
	; attempt to load the data 5 times
	mov		cx, 5				; 5 attempts
	mov		ah, 0x42			; extended read mode
	mov		si, DAP				; pass it the address of the DAP
	int		0x13				; call BIOS
	jnc		.finish				; leave if success
	loop	.reset_success		; loop otherwise

.error:
	stc
	ret

.finish:
	ret

;***************************************************************************
; compare 2 strings
; si = ptr to string 1
; di = ptr to string 2
; cx = length of strings
; sets carry on fail

CompString:
	; si and di point to the chars
	; save di & si
	push	di
	push	si

.loop:
	; check the char
	mov		al, [si]
	mov		bl, [di]
	inc		si
	inc		di
	cmp		al, bl
	jne		.fail
	loop	.loop

.success:
	pop		si
	pop		di
	clc
	ret

.fail:
	pop		si
	pop		di
	stc
	ret

;***************************************************************************
; print a string
; si = pointer to string to print

PrintString:
	; setup registers for video out mode
	mov		ah, 0x0e
	mov		bh, 0x00
	mov		bl, 0x07

.loop:
	; get char
	lodsb		; same as "mov	al, [si] + inc	si"

	; check for nullchar
	or		al, al
	jz		.finish

	; if not null, print it & repeat
	int		0x10
	jmp		.loop

.finish:
	ret


;****************************************************************************************
; protected mode flat GDT

prmd_gdt_table:
	; null
	dq 0

	; kernel code
	dw 0xffff
	dw 0x0000
	db 0x00
	db 10011010b
	db 11001111b
	db 0x00

	; kernel data
	dw 0xffff
	dw 0x0000
	db 0x00
	db 10010010b
	db 11001111b
	db 0x00

prmd_gdt_desc:
	dw $ - prmd_gdt_table - 1
	dd prmd_gdt_table

;****************************************************************************************
; long mode flat GDT

long_gdt_table:
	; null
	dq 0

	; kernel code
	dw 0
	dw 0
	db 0
	db 10011000b
	db 00100000b
	db 0

	; kernel data
	dw 0
	dw 0
	db 0
	db 10010000b
	db 00000000b
	db 0

long_gdt_desc:
	dw $ - long_gdt_table - 1
	dq long_gdt_table

;***************************************************************************
; pad to 8k
times 0x2000 - ($ - $$) db 0 


; pad to 28k
;times 0x7000 - ($ - $$) db 0